package server;

import interfaces.Category;
import interfaces.Core;
import interfaces.Customer;
import interfaces.Order;
import interfaces.Product;
import interfaces.ProductList;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.rmi.PortableRemoteObject;

public class CoreImpl extends PortableRemoteObject implements Core {
	private int nextCustomerId = 0;
	private HashMap<String, CustomerImpl> customers = null;
	private int nextOrderId = 0;
	private HashMap<String, OrderImpl> orders = null;
	private HashMap<Integer, OrderImpl> placedOrders = null;
	private ProductListImpl products = null;
	private HashMap<String, CategoryImpl> categories = null;
	private ReadWriteLock rwlock = null;

	/**
	 * Constructor
	 * 
	 * @throws RemoteException
	 */
	public CoreImpl() throws RemoteException {
		super();
		
		nextCustomerId = 0;
		customers = new HashMap<String, CustomerImpl>();
		nextOrderId = 0;
		orders = new HashMap<String, OrderImpl>();
		placedOrders = new HashMap<Integer, OrderImpl>();
		products = new ProductListImpl();
		categories = new HashMap<String, CategoryImpl>();
		rwlock = new ReentrantReadWriteLock();
	}

	/**
	 * Returns the Customer Object for the customer by the specified name
	 * 
	 * @param name	Customer name
	 * @return
	 * @throws RemoteException
	 */
	public synchronized Customer getCustomer( String name ) {
		Lock rlock = rwlock.readLock();
		rlock.lock();

		try {
			if( !customerExists( name ) ) {
				try {
					// Add new Customer
					customers.put( name, new CustomerImpl( nextCustomerId, name ) );
					
					// Increment next customer id
					nextCustomerId++;
				} catch( RemoteException e ) {
					e.printStackTrace();
				}
			}

			return (Customer)customers.get( name );
		} finally {
			rlock.unlock();
		}
	}

	/**
	 * Returns True if a Customer by the specified name exists, or False if not
	 * 
	 * @param name	Customer name
	 * @return
	 */
	private boolean customerExists( String name ) {
		return customers.get( name ) != null;
	}

	/**
	 * Returns the Order for the customer by the specified name
	 * 
	 * @param name	Customer name
	 * @return
	 * @throws RemoteException
	 */
	public synchronized Order getOrder( String name ) {
		Lock rlock = rwlock.readLock();
		rlock.lock();

		try {
			if( customerExists( name ) ) {
				if( !orderExists( name ) ) {
					try {
						// Add new Order
						orders.put( name, new OrderImpl( nextOrderId ) );
						
						// Increment next order id
						nextOrderId++;
					} catch( RemoteException e ) {
						e.printStackTrace();
					}
				}

				return (Order)orders.get( name );
			} else {
				return null;
			}
		} finally {
			rlock.unlock();
		}
	}

	/**
	 * Returns True if an Order exists for the customer by the specified name, or False if not
	 * 
	 * @param name	Customer name
	 * @return
	 */
	private boolean orderExists( String name ) {
		return orders.get( name ) != null;
	}

	/**
	 * Adds a Product to the Order for the customer by the specified name
	 * 
	 * @param name		Customer name
	 * @param product	Product
	 * @param amount	Amount
	 * @throws RemoteException
	 */
	public synchronized void addToOrder( String name, Product product, int amount ) {
		Lock wlock = rwlock.writeLock();
		wlock.lock();
		
		try {
			if( customerExists( name ) ) {
				getOrder( name ).add( product, amount );
			}
		} catch( RemoteException e ) {
			e.printStackTrace();
		} finally {
			wlock.unlock();
		}
	}

	/**
	 * Removes a Product from the Order for the customer by the specified name
	 * 
	 * @param name		Customer name
	 * @param product	Product
	 * @throws RemoteException
	 */
	public synchronized void removeFromOrder( String name, Product product ) {
		Lock wlock = rwlock.writeLock();
		wlock.lock();

		try {
			if( customerExists( name ) && product !=null ) {
				orders.get( name ).remove( product );
			}
		} finally {
			wlock.unlock();
		}
	}
	
	/**
	 * Permanently places the order
	 * 
	 * @param name	Customer name
	 * @throws RemoteException
	 */
	public void placeOrder( String name ) {
		if( customerExists( name ) ) {
			if( orderExists( name ) ) {
				OrderImpl order = orders.get( name );
				
				// Put order in placed orders list
				placedOrders.put( order.getId(), order );
				
				// Remove customer and order
				orders.remove( name );
				customers.remove( name );
			}
		}
	}

	/**
	 * Adds a Product to the product list
	 * 
	 * @param name		Product name
	 * @param product	Product Object
	 */
	public synchronized void addToProductList( ProductImpl product ) {
		Lock wlock = rwlock.writeLock();
		wlock.lock();

		try {
			products.add( product );
		} finally {
			wlock.unlock();
		}
	}

	/**
	 * Returns a Product from the product list by the specified name
	 * 
	 * @param name		Product name
	 * @return
	 * @throws RemoteException
	 */
	public synchronized Product getProduct( String name ) {
		Lock rlock = rwlock.readLock();
		rlock.lock();

		try {
			return products.get( name );
		} finally {
			rlock.unlock();
		}
	}

	/**
	 * Returns the ProductList
	 * 
	 * @return
	 * @throws RemoteException
	 */
	public ProductList getProductList() {
		return (ProductList)products;
	}
	
	/**
	 * Adds a Category to the categories list
	 * 
	 * @param name	Category name
	 */
	public synchronized void addCategory( String name ) {
		Lock wlock = rwlock.writeLock();
		wlock.lock();
		
		try {
			categories.put( name, new CategoryImpl( categories.size(), name ) );
		} catch( RemoteException e ) {
			e.printStackTrace();
		} finally {
			wlock.unlock();
		}
	}
	
	/**
	 * Returns a list of Categories
	 * 
	 * @return
	 * @throws RemoteException
	 */
	public synchronized ArrayList<Category> getCategories() {
		Lock rlock = rwlock.readLock();
		rlock.lock();
		
		try {
			ArrayList<Category> tempCategories = new ArrayList<Category>();
			for( CategoryImpl category: categories.values() ) {
				tempCategories.add( (Category)category );
			}
			
			return tempCategories;
		} finally {
			rlock.unlock();
		}
	}
	
	/**
	 * Returns a Category by the specified name
	 * 
	 * @param name	Category name
	 * @return
	 * @throws RemoteException
	 */
	public synchronized CategoryImpl getCategory( String name ) {
		Lock rlock = rwlock.readLock();
		rlock.lock();
		
		try {
			return categories.get( name );
		} finally {
			rlock.unlock();
		}
	}
}
